﻿using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Reflection;
using System.Web.Mvc;
using System.Web.Routing;

namespace Entropy.Ext
{
    public class ColumnFluent<TModel>
    {
        private readonly List<MemberInfo> members;
        private readonly List<IColumn> columns;

        public ColumnFluent(List<MemberInfo> members, List<IColumn> columns)
        {
            this.members = members;
            this.columns = columns;
        }


        public static implicit operator IColumn[](ColumnFluent<TModel> model)
        {
            return model.columns.ToArray();
        }

        private static MemberInfo ExtractMember(Expression expr)
        {
            if (expr.NodeType != ExpressionType.Assign)
                throw new Exception("Assign expected but found:" + expr.NodeType);

            var bin = (BinaryExpression)expr;

            var rightMember = ((MemberExpression)bin.Right).Member;
            return rightMember;
        }

        private IColumn FindColumn(Expression expr)
        {
            int index = members.FindIndex(m => m == ExtractMember(expr));
            if (index < 0)
                throw new Exception("Unexpected member: " + expr);
            return columns[index];
        }

        public ColumnFluent<TModel> ForMember(Expression<Func<TModel, bool>> member, Action<IBooleanColumn> configure)
        {
            configure((IBooleanColumn)FindColumn(member));
            return this;
        }

        public ColumnFluent<TModel> ForMember(Expression<Func<TModel, int>> member, Action<INumberColumn> configure)
        {
            configure((INumberColumn)FindColumn(member));
            return this;
        }
    }

    /// <summary>
    /// Feature!
    /// When we provide ajax data for the ExtJs DataStore, we would like to also provide totalCount, message etc.
    /// Lets encapsulate it inside predefined kind of response and autowrap on client side
    /// </summary>
    public static class StoreExtJsExtensions
    {
        private static IAjaxProxy UrlFromRouteValues(IExtFactory ext, RouteValueDictionary routeValues, IDataReader reader, bool includeParameters)
        {
            var page = ExtFactoryProxy.PageByExt(ext);
            var url = ActionLinkExtension.StringifyActionUrl(page.Html, routeValues, includeParameters);
            var writer = ext.JsonWriter(allowSingle: false);
            return ext.CreateAjaxProxy(url: url, reader: reader, writer: writer);
        }

        public static IStore ServerStoreWithoutModel<TController>(this IExtFactory ext, Expression<Action<TController>> action) where TController : Controller
        {
            var reader = ext.JsonReader(root: ExtStoreViewModel.RootProperty);
            var proxy = UrlFromRouteValues(ext, ActionLinkExtension.GetRouteValuesFromExpression(action), reader, false);
            return ext.CreateDataStore(autoLoad: false, proxy: proxy);
        }

        public static ColumnFluent<TModel> ServerColumns<TModel>(this IExtFactory ext)
        {
            var members = new List<MemberInfo>();
            var columns = new List<IColumn>();

            foreach (var prop in typeof(TModel).GetProperties())
            {
                members.Add(prop);
                string header = ColumnHeader(prop);
                var visible = ColumnVisible(prop);
                var displayWidth = ColumnWidth(prop);
                if (!visible)
                    continue;
                bool? useNull;
                string dateFormat;
                switch (ResolveFieldType(prop, out useNull, out dateFormat))
                {
                    case FieldType.Float:
                        columns.Add(ext.NumberColumn(
                            dataIndex: prop.Name,
                            text: header,
                            sortable: true,
                            align: ColumnAlign.Right
                        ));
                        break;
                    case FieldType.Int:
                        columns.Add(ext.NumberColumn(
                            dataIndex: prop.Name,
                            text: header,
                            sortable: true,
                            format: "0",
                            align: ColumnAlign.Right
                        ));
                        break;

                    case FieldType.Date:
                        //var dateOnly = prop.GetCustomAttribute<DateOnlyAttribute>() != null;

                        columns.Add(ext.DateColumn(
                            dataIndex: prop.Name,
                            text: header,
                            sortable: true
                            //							renderer: dateOnly ? ext.DateRenderer("m-d-Y") : null
                        ));
                        break;

                    case FieldType.Boolean:
                        columns.Add(ext.BooleanColumn(
                            dataIndex: prop.Name,
                            text: header,
                            sortable: true
                        ));
                        break;

                    default:
                        columns.Add(ext.Column(
                            width: displayWidth,
                            dataIndex: prop.Name,
                            text: header,
                            sortable: true
                        ));
                        break;
                }
            }

            return new ColumnFluent<TModel>(members, columns);
        }

        private static string ColumnHeader(PropertyInfo prop)
        {
            string header = null;
            var attr = prop.GetCustomAttribute<DisplayAttribute>();
            if (attr != null)
            {
                header = attr.Name;
            }

            if (string.IsNullOrEmpty(header))
                header = prop.Name;
            return header;
        }

        private static bool ColumnVisible(PropertyInfo prop)
        {
            var visible = true;
            var attr = prop.GetCustomAttribute<BrowsableAttribute>();
            if (attr != null)
                visible = attr.Browsable;
            return visible;
        }

        private static int? ColumnWidth(PropertyInfo prop)
        {
            var attr = prop.GetCustomAttribute<MaxLengthAttribute>();
            if (attr != null)
                return attr.Length * 6;
            return null;
        }

        public static IStore ServerStore<TController, TModel>(this IExtFactory ext, Expression<Func<TController, StoreResult<TModel>>> action, bool includeParameters = false, bool autoLoad = true, bool autoSync = false, int? pageSize = null) where TController : Controller
        {
            string idProperty;
            var fields = ResolveFields(ext, typeof(TModel), out idProperty);
            var reader = ext.JsonReader(root: ExtStoreViewModel.RootProperty, idProperty: idProperty);
            var proxy = UrlFromRouteValues(ext, ActionLinkExtension.GetRouteValuesFromExpression(action), reader, includeParameters);
            return
                ext.CreateDataStore(
                    autoLoad: autoLoad,
                    autoSync: autoSync,
                    proxy: proxy,

//					buffered: true,
                    pageSize: pageSize,
                    fields: fields
                );
        }

        public static IStore ServerStore(this IExtFactory ext, string url, Type modelType, bool includeParameters = false)
        {
            string idProperty;
            var fields = ResolveFields(ext, modelType, out idProperty);
            var reader = ext.JsonReader(root: ExtStoreViewModel.RootProperty, idProperty: idProperty);
            var proxy = ext.CreateAjaxProxy(url: url, reader: reader);
            return
                ext.CreateDataStore(
                    autoLoad: false,
                    proxy: proxy,
                //					buffered: true,
                //					pageSize: 25,
                    fields: fields
                );
        }

        private static IDataField[] ResolveFields(IExtFactory ext, Type type, out string idProperty)
        {
            idProperty = null;
            var result = new List<IDataField>();
            foreach (var prop in type.GetProperties())
            {
                Debug.Assert(prop != null);
                bool? useNull;
                string dateFormat;
                FieldType fieldType = ResolveFieldType(prop, out useNull, out dateFormat);
                result.Add(ext.DataField(name: prop.Name, type: fieldType, useNull: useNull, dateFormat: dateFormat));
                if (prop.GetCustomAttribute<KeyAttribute>() != null)
                    idProperty = prop.Name;
            }
            return result.ToArray();
        }

        private static FieldType ResolveFieldType(PropertyInfo prop, out bool? useNull, out string dateFormat)
        {
            FieldType fieldType;
            useNull = null;
            dateFormat = null;
            var type = prop.PropertyType;
            bool nullable = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
            if (nullable)
            {
                type = type.GetGenericArguments()[0];
                useNull = true;
            }

            var propertyType = type;
            if (propertyType == typeof(int) ||
                propertyType == typeof(byte) ||
                propertyType == typeof(long) ||
                propertyType == typeof(short))
                fieldType = FieldType.Int;
            else if (propertyType == typeof(bool))
                fieldType = FieldType.Boolean;
            else if (propertyType == typeof(DateTime))
            {
                fieldType = FieldType.Date;
                dateFormat = "c";
            }
            else if (propertyType == typeof(double) || propertyType == typeof(decimal) || propertyType == typeof(float))
                fieldType = FieldType.Float;
            else if (propertyType == typeof(string))
                fieldType = FieldType.String;
            else
                fieldType = FieldType.String;
            return fieldType;
        }
    }
}